跳到主要内容

渲染进程加载本地资源的方案?file:// 有什么问题?

考察点​

  • file:// 协议的限制认知
  • 自定义协议加载资源的完整实践

参考答案​

1. file:// 加载的问题​

win.loadFile('index.html') 本质是 file:// 协议加载,开发简单但限制多:

问题说明
fetch/XHR 受限file:// 下 fetch 本地文件被 CORS 拦截(file origin 为 null)
ES Module 加载失败<script type="module"> 的 import 在 file:// 下跨文件引用受限
origin 是 nulllocalStorage/IndexedDB 按 null origin 处理,行为怪异且多窗口可能共享
安全上下文非 secure context,crypto.subtle 等 API 不可用
Service Worker无法注册
路径分隔符/编码Windows 反斜杠、中文路径编码的边角问题

2. 现代方案:自定义协议(protocol.handle)​

// ready 前注册特权 scheme
protocol.registerSchemesAsPrivileged([
{
scheme: 'myapp',
privileges: {
standard: true, // 标准 URL 解析
secure: true, // 安全上下文(crypto.subtle 可用)
supportFetchAPI: true, // fetch 可用
corsEnabled: true,
stream: true,
},
},
]);

app.whenReady().then(() => {
// 注册处理器:把 myapp:// 映射到本地文件
protocol.handle('myapp', (request) => {
const { pathname } = new URL(request.url);
const filePath = path.resolve(RENDERER_ROOT, '.' + decodeURIComponent(pathname));

// 防路径穿越(见安全篇)
if (!filePath.startsWith(RENDERER_ROOT + path.sep)) {
return new Response('forbidden', { status: 403 });
}
return net.fetch(pathToFileURL(filePath).toString());
});

win.loadURL('myapp://app/index.html'); // 页面有了正常的 origin
});

效果:myapp://app 成为标准 origin,fetch、module、storage、安全上下文全部正常。

3. 用户文件/动态资源的加载​

应用资源(打包内的 html/js/css)走 myapp:// 协议;用户文件(图片、视频、文档,路径动态且不在安装目录)需要另一个通道:

(1)专用媒体协议

// media:// 协议专门服务用户文件
protocol.handle('media', (request) => {
const { pathname } = new URL(request.url);
const filePath = decodeURIComponent(pathname);

// 白名单根目录(只允许用户数据目录下的文件)
const allowed = path.resolve(filePath).startsWith(USER_DATA_ROOT);
if (!allowed) return new Response('forbidden', { status: 403 });

return net.fetch(pathToFileURL(filePath).toString());
});
<!-- 渲染进程使用 -->
<img src="media:///Users/xxx/Library/myapp/cache/photo.png" />
<video src="media:///path/to/video.mp4" /> <!-- stream: true 支持拖动播放 -->

(2)转 data URL / blob(小文件场景)

// 小图标等小文件,主进程读文件转 base64 经 IPC 返回
const dataUrl = await window.api.readAsDataUrl(filePath);
img.src = dataUrl;

缺点:大文件内存翻倍(文件内容 + base64 字符串 + 解码后数据),只用于小文件。

4. 方案选型表​

资源类型方案
应用页面/静态资源自定义协议(myapp://)
用户媒体文件(图/视频)专用媒体协议 + 根目录白名单
小文件(几 KB 图标)IPC 转 data URL
超大文件(GB 级视频流)本地 HTTP 服务(Range 支持好)

5. 注意点​

  1. registerSchemesAsPrivileged 必须在 ready 前,忘了调会发现 fetch/storage 全异常
  2. 协议 handler 里必须防路径穿越(resolve 后校验根目录前缀)
  3. stream: true 让协议响应支持流式(视频拖动播放、大文件边下边播)
  4. 开发环境用 vite dev server(http://localhost),生产用自定义协议——两种环境 origin 不同,代码里不要硬编码 origin
  5. CSP 配置要把自定义 scheme 加进白名单(img-src myapp: media:)

面试话术​

file:// 的问题可以归结为"origin 是 null"引发的一系列异常:fetch 被 CORS 拦、ES Module 受限、storage 行为怪异、非安全上下文。标准解法是注册特权自定义协议加载应用资源(ready 前注册特权、handle 里防路径穿越);用户文件走独立媒体协议 + 根目录白名单,小文件才用 data URL 偷懒,GB 级流媒体走本地 HTTP 服务。

常见追问​

  • dev 用 http dev server、prod 用自定义协议,代码怎么兼容? 构建时注入 BASE_URL 环境变量,资源引用全部走变量;或以 dev server 为准,prod 协议 handler 内部转发到同样的路径结构。
  • 协议响应慢怎么优化? handler 里做缓存(静态资源哈希指纹 + 强缓存头);避免每次请求都 stat 文件。